# 导入 pygame 库，所有图形、声音、事件处理的基础
import pygame
# 导入 sys 库，主要用于安全地退出程序 (sys.exit)
import sys
# 导入 random 库，用于生成随机数，实现分子和粒子的随机行为
import random

# 科技感配色体系（蓝黄主色调）
# 定义一个常量，表示界面的背景色 (RGB值，一种非常深的蓝色，接近黑色)
TECH_BG = (8, 12, 28)  # 深空黑底
MOLECULE_BLUE = (0, 191, 255)  # 分子蓝色
MOLECULE_YELLOW = (255, 215, 0)  # 分子黄色
# 定义背景网格线的颜色 (RGBA值，第四个值40代表透明度，让网格线很淡)
GRID_COLOR = (0, 120, 200, 40)  # 透明蓝网格
PARTICLE_BLUE = (0, 170, 255, 180)  # 蓝色粒子
PARTICLE_YELLOW = (255, 200, 0, 180)  # 黄色粒子
TITLE_GRADIENT_START = (0, 220, 255)  # 标题渐变起点，(亮蓝色)
TITLE_GRADIENT_END = (255, 230, 0)  # 标题渐变终点，(亮黄色)
BUTTON_BG = (0, 150, 220, 60)  # 按钮背景，定义按钮的普通背景色 (带透明度的蓝色)
BUTTON_HOVER_BG = (255, 200, 0, 70)  # 按钮hover效果，定义鼠标悬停在按钮上时的背景色 (带透明度的黄色)

# --- 2. 定义分子类 ---
# 这个类负责定义和控制屏幕上每一个游离的分子小球
class Molecule:
    """蓝黄分子交织核心类"""

    # 类的初始化方法（构造函数），当创建一个 Molecule 对象时会自动调用
    def __init__(self, screen_w, screen_h, color):
        # 保存屏幕的宽高，用于后续的边界检测
        self.screen_w = screen_w
        self.screen_h = screen_h
        # 随机生成分子的初始 x, y 坐标，让它出现在屏幕内部
        self.x = random.randint(50, screen_w - 50)
        #random.randint(a, b)  # 生成一个 [a, b] 之间的随机整数（包括 a 和 b）
        self.y = random.randint(50, screen_h - 50)
        # 随机生成分子的半径大小
        self.radius = random.randint(3, 8)
        # 设置分子的颜色 (由创建时传入)
        self.color = color
        # 随机生成 x, y 方向的移动速度，random.choice([-1, 1]) 决定了方向是正还是负
        self.speed_x = random.choice([-1, 1]) * random.uniform(0.2, 0.5)
        """
        1.random.choice([-1, 1]) — 决定方向,random.choice([-1, 1]) 会从 [-1, 1] 这个列表中随机挑选一个数字。
        结果要么是 -1，要么是 1，各有一半的概率。
        +1 代表正方向（在 Pygame 里，X 轴是向右，Y 轴是向下）。
        -1 代表负方向（X 轴是向左，Y 轴是向上）。
        如果你只写 self.speed_x = random.choice([-1, 1])，那么速度的大小就永远是 1 或者 -1，固定不变。
        所有的物体都会以完全相同的速度移动，只是方向不同而已。
        2.random.uniform(0.2, 0.5) — 决定速度有多快,random.uniform 的意思是“随机取一个范围内的浮点数（小数）”。
        random.uniform(0.2, 0.5) 会随机返回一个在 0.2 到 0.5 之间的任何小数（比如 0.25, 0.38, 0.49 等）
        0.2 代表比较慢。  0.5 代表比较快。  中间的数值代表各种快慢不一的速度。
        如果你只写 self.speed_x = random.uniform(0.2, 0.5)，那么你得到的速度永远是正数。
        这意味着你的物体永远只会向正方向移动（永远向右，永远向下），它永远学不会向左或向上移动。这样方向就不随机了。
        """

        self.speed_y = random.choice([-1, 1]) * random.uniform(0.2, 0.5)
        # 缩放方向，1表示正在放大
        self.scale_dir = 1  # 缩放方向
        # 随机的缩放速度
        self.scale_speed = random.uniform(0.01, 0.03)
        #random.uniform(x, y)  # 生成一个 [x, y] 或 [y, x] 之间的随机浮点数（包含边界）
        # 保存初始半径，用于计算缩放范围
        self.base_radius = self.radius

    # 类的更新方法，每一帧都会被调用，用于更新分子的状态
    def update(self):
        """更新分子位置和缩放状态"""
        # 边界反弹
        # 边界碰撞检测与反弹
        # 如果分子的左边缘碰到屏幕左边界，或者右边缘碰到右边界
        if self.x - self.radius <= 0 or self.x + self.radius >= self.screen_w:
            # 就把 x 方向的速度取反，实现反弹效果
            self.speed_x *= -1
        # 对 y 方向做同样的处理
        if self.y - self.radius <= 0 or self.y + self.radius >= self.screen_h:
            self.speed_y *= -1

        # 根据速度更新分子的 x, y 坐标
        self.x += self.speed_x  #效果就是，这个分子在平滑地向右移动。是用来应用“速度”，真正改变“位置”的。
        self.y += self.speed_y

        # 呼吸缩放效果
        # 实现呼吸灯一样的缩放效果
        # 半径根据缩放方向和速度进行变化
        self.radius += self.scale_dir * self.scale_speed
        # 如果半径达到了基础半径的1.5倍
        if self.radius >= self.base_radius * 1.5:
            # 就把缩放方向改为缩小
            self.scale_dir = -1
        # 如果半径缩小到了基础半径的0.7倍
        elif self.radius <= self.base_radius * 0.7:
            # 就把缩放方向改为放大
            self.scale_dir = 1

    # 类的绘制方法，每一帧调用，把分子画出来
    def draw(self, screen, other_molecules):
        """绘制分子及连接线条"""
        # 外发光效果
        # --- 绘制外发光效果 ---
        # 创建一个临时的、支持透明的 Surface 画布，大小是分子半径的4倍
        glow_surface = pygame.Surface((self.radius * 4, self.radius * 4), pygame.SRCALPHA)
        """
        1.pygame.SRCALPHA 是你传递给这个“画布”的一个“标记” (Flag)。    SRCALPHA 的意思是 "Source Alpha"（源 Alpha）。
        它的功能是： 告诉 Pygame：“请把这块新创建的 glow_surface 画布，设置为‘支持透明度’的模式。”
        2.self.color[:3] (切片) 
        [:3] 是 Python 的“切片”语法，意思是“获取从开头到索引 3 为止（但不包括 3）的所有元素”。
        “安全地”只取出前三个元素（R, G, B）
        3.*... (解包)
        * (星号) 在这里是**“解包” (Unpacking)** 运算符。
        它会把上一步得到的元组 (255, 100, 50)“打散”成三个独立的数字：255, 100, 50。
        4.(*..., 30) (重新打包) ( ... , 30) 这个括号会把这些打散的元素重新组合成一个新的元组。
        它的意思是：“把 255, 100, 50 这三个数字放进来，然后在最后面追加一个 30。”
        最终结果：创建了一个新的元组 (255, 100, 50, 30)
        5.R: 255    G: 100      B: 50       A (Alpha / 透明度): 30
        在 Pygame 中, 透明度范围是 0 (完全透明) 到 255 (完全不透明)。30 是一个非常低的值，所以这个颜色非常透明。
        """
        # 在这个临时画布的中心画一个半透明的、更大的圆，作为光晕
        # (*self.color[:3], 30) 的意思是取分子颜色RGB值，然后加上透明度30
        pygame.draw.circle(glow_surface, (*self.color[:3], 30), (self.radius * 2, self.radius * 2), self.radius * 2)
        # 把这个带有光晕的临时画布，画到主屏幕上，位置与分子对齐
        """
        1.blit 功能是：把一个“小画布”（Surface）复制（画）到另一个“大画布”（Surface）上
        2.大画布：就是你的主屏幕 screen。
        小画布：就是任何你加载的图片、创建的临时画布等，比如这里的 glow_surface
        3.blit 函数需要你告诉它 2 件事情： 大画布.blit(你要画的小画布, 画在什么位置)
    screen.blit(...): 这表示你准备在 screen (主屏幕) 这个“大画布”上进行操作
    glow_surface (第一个参数):你要画什么，
    (self.x - self.radius * 2, self.y - self.radius * 2) (第二个参数):这是“你要画在什么位置”。
    最关键的一点：blit 使用的这个坐标，永远是你要画的那个“小画布”的左上角的坐标。
        """
        screen.blit(glow_surface, (self.x - self.radius * 2, self.y - self.radius * 2))


        # 分子核心
        # --- 绘制分子核心 ---
        # 在屏幕上画一个实心圆，代表分子的核心
        pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)), int(self.radius))

        # 与其他分子的连接线
        # 遍历传入的所有其他分子
        for mol in other_molecules:
            # 使用勾股定理计算当前分子与另一个分子之间的直线距离
            distance = ((self.x - mol.x) ** 2 + (self.y - mol.y) ** 2) ** 0.5
            # 如果距离小于120像素（一个阈值）
            if distance < 120:
                # 就计算线条的透明度，距离越远，透明度越低（线条越淡）
                alpha = int(100 - (distance / 120) * 80)
                # 计算线条的颜色，是两个分子颜色的平均值，实现颜色过渡
                line_color = (
                    (self.color[0] + mol.color[0]) // 2,
                    (self.color[1] + mol.color[1]) // 2,
                    (self.color[2] + mol.color[2]) // 2,
                    alpha
                )
                # 在两个分子之间画一条半透明的线
                pygame.draw.line(screen, line_color, (int(self.x), int(self.y)), (int(mol.x), int(mol.y)), 1)

# --- 3. 定义粒子流类 ---
# 这个类负责管理从屏幕边缘飞向中心的粒子流效果
class ParticleStream:
    """流向中心的粒子流效果"""

    # 初始化方法
    def __init__(self, screen_w, screen_h, color):
        self.screen_w = screen_w
        self.screen_h = screen_h
        self.color = color
        # 调用内部方法，生成初始的一批粒子
        self.particles = self._generate_particles()

    # 内部方法，用于生成一批新的粒子
    def _generate_particles(self):
        """生成从边缘流向中心的粒子"""
        # 创建一个空列表存放新粒子
        particles = []
        # 一次生成15个
        """
        for _ in range(15),_ (下划线) 是一个“占位符”，它是一个完全有效的变量名
        约定俗成的习惯用法。当你看到 for _ in ... 时，它向你（或其他程序员）传递了一个清晰的信号：
        我需要在这里循环 15 次，但是我‘不在乎’每一次循环具体是第几次。这个循环变量我用不上，所以我就用一个‘无所谓’的名字 _ 来代替它。
        """
        for _ in range(15):
            # 随机决定粒子从哪个边缘（上、下、左、右）出生
            edge = random.choice(["top", "bottom", "left", "right"])
            # 根据出生边缘，设置粒子的初始 x, y 坐标，让它在屏幕外一点点
            if edge == "top":
                x = random.randint(0, self.screen_w)
                y = -10
            elif edge == "bottom":
                x = random.randint(0, self.screen_w)
                y = self.screen_h + 10
            elif edge == "left":
                x = -10
                y = random.randint(0, self.screen_h)
            else:
                x = self.screen_w + 10
                y = random.randint(0, self.screen_h)

            # 计算流向中心的方向，计算从出生点指向屏幕中心的方向向量
            dir_x = (self.screen_w // 2 - x) / 100
            dir_y = (self.screen_h // 2 - y) / 100
            # 除以100是为了“归一化”，让向量变短，方便后续乘以速度

            # 将粒子的所有属性打包成一个字典，添加到列表中
            particles.append({
                "x": x, "y": y, # 坐标
                "dir_x": dir_x, "dir_y": dir_y, # 方向
                "length": random.randint(3, 8), # 拖尾长度
                "speed": random.uniform(1.5, 3),    # 移动速度
                "life": random.randint(60, 120)     # 生命周期（单位：帧）
            })
        return particles

    # 更新方法，更新所有粒子的状态
    def update(self):
        """更新粒子状态"""
        # 遍历粒子列表的副本 ([:])，这样可以在循环中安全地删除元素
        for particle in self.particles[:]:
            # 根据方向和速度，更新粒子的 x, y 坐标
            """
            self.particles 是一个列表 (list),但是particle (列表中的单个元素) 不是一个列表，而是一个字典 (Dictionary)，
            particle 是里面的元素，元素是字典，字典["键"](particle["x"]) 就是从这个字典中获取“键”对应的“值”的标准 Python 语法。
            """
            particle["x"] += particle["dir_x"] * particle["speed"]
            particle["y"] += particle["dir_y"] * particle["speed"]
            # 生命周期减一
            particle["life"] -= 1
            # 如果生命周期结束
            if particle["life"] <= 0:
                # 就从列表中移除这个粒子
                self.particles.remove(particle)
        # 如果粒子数量太少
        if len(self.particles) < 10:
            # 就再补充5个新粒子，实现源源不断的效果
            self.particles.extend(self._generate_particles()[:5])

    # 绘制方法，画出所有粒子
    def draw(self, screen):
        """绘制粒子流"""
        for particle in self.particles:
            # 根据剩余生命，计算粒子的透明度，实现淡入淡出
            alpha = int((particle["life"] / 120) * 150)
            if alpha <= 0:
                # 如果完全透明了，就不画了
                continue

            # 计算粒子拖尾的终点坐标
            end_x = particle["x"] + particle["dir_x"] * particle["length"]
            end_y = particle["y"] + particle["dir_y"] * particle["length"]

            # 画一条带透明度的线，模拟流星拖尾效果
            pygame.draw.line(
                screen,
                (*self.color[:3], alpha),
                (int(particle["x"]), int(particle["y"])),
                (int(end_x), int(end_y)),
                2
            )
            """
            1.pygame.draw.line 是 Pygame 库自带的、专门用来画直线的核心函数。
            2.功能非常专一：在指定的“画布”上，画一条从“起点”到“终点”的直线
            3.pygame.draw.line(surface, color, start_pos, end_pos, width)
            surface (画布)    color (颜色)      start_pos (起点坐标)        end_pos (终点坐标)      width (粗细) - [可选]
            4.坐标必须是整数,   
            5.pygame.draw.line 画出来的线是**“有锯齿”**的，如果你画一条斜线，你会看到它是由一个一个像素方块组成的，可能不好看
            如果你想要画一条**“平滑的、抗锯齿”的线，你应该使用另一个函数**：
             pygame.draw.aaline(surface, color, start_pos, end_pos)
             aaline (Anti-Aliased Line) 的效果更漂亮，但注意：aaline 不支持 width (粗细) 参数，它画的线永远是 1 像素粗。
            """

# --- 4. 定义渐变文字工具类 ---
# 这是一个工具类，提供了一个静态方法来绘制渐变色的文字
class GradientText:
    """绘制渐变文字的工具类"""

    # @staticmethod 表示这是一个静态方法，调用它不需要创建 GradientText 的实例
    # 可以直接通过 GradientText.draw_gradient_text(...) 来调用
    @staticmethod
    def draw_gradient_text(screen, text, font, pos, start_color, end_color):
        """绘制水平渐变文字"""
        # 1. 先用一种任意的颜色把文字渲染出来，主要是为了获取它的尺寸和形状
        """
        font.render() 函数只能用一种纯色来画文字（比如全红或全蓝），它不能直接画渐变色。
        1.制作“筛子”（font.render）：先用任意一种颜色（这里选了灰色 (120,120,120)），把文字画在一块临时的、看不见的画布 text_surf 上
        这一步就像是制作一个“筛子”。筛子的形状就是你要的文字（比如 "START"），筛孔（透明的部分）就是文字周围的空白区域。
        关键点：我们不关心这个文字是什么颜色（灰色），我们只关心它的形状和大小（text_rect）
        """
        text_surf = font.render(text, True, (120, 120, 120))
        """
        把文字渲染成一个图像
        1.调用了 font 对象的 render (渲染) 方法,text (要渲染的文字)     True (是否开启“抗锯齿”):True (是)：Pygame 会尝试让文字的边缘变得平滑，看起来更漂亮。
           (120, 120, 120) (文字的颜色)   font.render 只能使用一种纯色       render 函数在工作完成后，会返回一个全新的 pygame.Surface 对象。
           text_surf = ... (保存结果):
           text_surf (text surface) 就是一块临时的、只画着你的灰色文字的“小画布”（也就是一个图像）。
        """
        text_rect = text_surf.get_rect(topleft=pos)
        """
        topleft=pos
        语法永远是 参数名 = 值
        获取这张文字图片的大小和位置
        1.text_surf.get_rect(),对刚刚创建的 text_surf (文字图片) 调用 .get_rect() 方法。它的功能是:
        创建一个看不见的矩形 (pygame.Rect)，这个矩形的宽度和高度与 text_surf 这张图片一模一样
        2.(topleft=pos)     这是 .get_rect() 的一个“快捷方式”
        3.text_rect = ... (保存结果):text_rect (text rectangle) 变量现在存储了这个看不见的矩形。
        这个 text_rect 对象现在同时知道两件事：文字有多大（宽度和高度）。  文字应该被画在屏幕的什么位置（由 pos 决定）
        """
        # 2. 创建一个和文字一样大的、支持透明的临时画布
        gradient_surf = pygame.Surface(text_rect.size, pygame.SRCALPHA)
        """
        pygame.Surface(...) 是 Pygame 的一个核心功能，Surface 在 Pygame 的世界里就代表“一块画布”或“一个图层”
        pygame.Surface(...) 的作用就是**“凭空创建一块全新的、空白的画布”**。
        第一个参数: text_rect.size
        text_rect.size (画布有多大？)
        text_rect 是你上一行代码得到的，包含了文字位置和大小的那个隐形矩形。
        .size 是 pygame.Rect 对象的一个属性，它会返回一个元组，比如 (120, 30)，代表这个矩形的（宽度, 高度）。
        所以，把 text_rect.size 作为第一个参数，就保证了这块新创建的画布 gradient_surf，它的尺寸和你的文字图片的尺寸完全一样。不大不小，刚刚好。
        第二个参数: pygame.SRCALPHA (画布是什么类型？)   这是一个非常重要的**“标记” (Flag)**。
        SRC 是 "Source" (源) 的缩写。
        ALPHA 是 "Alpha Channel" (透明度通道) 的缩写。
        pygame.SRCALPHA 这个标记告诉 pygame.Surface：“请创建一个支持逐像素透明度的特殊画布。”
        """
        # 3. 在这个临时画布上画出颜色渐变
        # 遍历画布的每一个垂直列 (x)
        for x in range(text_rect.width):
            # 计算当前 x 坐标在总宽度中的比例 (0.0 到 1.0)
            ratio = x / text_rect.width
            """
            ratio 是什么：它是一个从 0.0 到 1.0 的小数，代表了你“刷墙”的进度，“比例”是混合颜色的关键
            """
            # 根据这个比例，线性插值计算出当前列应该是什么颜色
            r = int(start_color[0] * (1 - ratio) + end_color[0] * ratio)
            g = int(start_color[1] * (1 - ratio) + end_color[1] * ratio)
            b = int(start_color[2] * (1 - ratio) + end_color[2] * ratio)
            # 在这一列画一条垂直的、当前颜色的线
            pygame.draw.line(gradient_surf, (r, g, b), (x, 0), (x, text_rect.height), 1)
        """
        for循环的结果是：
        gradient_surf 这块画布，现在完完全全被涂满了一整块矩形的渐变色。
         它就像一块从蓝色渐变到红色的“彩色布料”。它没有任何文字形状，它就是一块完整的、实心的渐变色矩形。
        """
        # 4. 利用混合模式将渐变色应用到文字上
        # 将第一步渲染的文字图像，以“正片叠底”(BLEND_RGBA_MULT)模式画到渐变画布上
        # 这样，文字的非透明部分就会染上对应位置的渐变色
        """
        要blit:
        我们现在的目标是用“文字形状”去“裁剪”这块“彩色布料”
        """
        gradient_surf.blit(text_surf, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)
        """
        gradient_surf（渐变色布料）在调用 blit,我要往我（gradient_surf）自己身上，再贴一层东西。”
        1.text_surf (第一个参数)：贴什么呢？贴我们第一步创建的那个灰色文字“筛子” (text_surf)。
        2.special_flags=pygame.BLEND_RGBA_MULT (最关键的魔法)：special_flags 的意思是“特殊混合模式”。
        BLEND_RGBA_MULT 是一种混合模式，叫做“正片叠底”或“相乘模式”。
        ①凡是“筛子” (text_surf) 上是“透明”的地方（也就是文字周围的空白区域）：
        混合后的结果也是**“透明”**。
        效果：这一下就把“渐变色布料”上，文字以外的所有区域都“抠掉”了！
        ②凡是“筛子” (text_surf) 上是“不透明”的地方（也就是灰色的文字笔画）：
        混合后的结果是保留“渐变色布料”底层的颜色。
        效果：文字笔画所在的地方，被染上了对应的渐变色
        """
        # 5. 最后，把制作好的、带有渐变色的文字画布，画到主屏幕上
        screen.blit(gradient_surf, text_rect)
        return text_rect


def draw_start_screen(screen, screen_w, screen_h, sound_manager):
    """开始界面主逻辑"""
    pygame.mixer.music.set_volume(0.4)
    """
    1.pygame.mixer (工具箱)    mixer 在英文里是“混音器”的意思。    pygame.mixer 是 Pygame 库里专门用来处理所有声音的“音频工具箱”。
    2.pygame.mixer.music (专门的播放器)
    这是 mixer 工具箱里一个专门的“背景音乐（BGM）播放器”。
    Pygame 把“背景音乐”（通常是长的 mp3、ogg 文件）和“音效”（短的 wav 文件）分开处理。music 就是用来管 BGM 的。
    3..set_volume() (功能/函数) set 的意思是“设置”。   volume 的意思是“音量”。    
    set_volume() 是 music 播放器对象上的一个函数（或方法）。  它的功能非常专一：“设置这台背景音乐播放器的音量”。
    你可以把它想象成这台“BGM 播放器”上的**“音量旋钮”**
    """
    clock = pygame.time.Clock()

    # 初始化分子和粒子流
    # 初始化动态背景元素
    # 创建15个蓝色分子
    blue_molecules = [Molecule(screen_w, screen_h, MOLECULE_BLUE) for _ in range(15)]
    """
    1.blue_molecules = [...] 是在干什么？ [...] 这种语法叫做“列表推导式” (List Comprehension)。它的作用是“批量生产”。
    2.请把 Molecule(...) 这个‘创建分子’的动作，给我重复执行 15 遍 (for _ in range(15))，
    然后把造出来的这 15 个分子对象，全都装进一个列表里，并把这个列表命名为 blue_molecules
    """
    # 创建15个黄色分子
    yellow_molecules = [Molecule(screen_w, screen_h, MOLECULE_YELLOW) for _ in range(15)]
    # 把它们合并到一个大列表里，方便进行连接线计算
    all_molecules = blue_molecules + yellow_molecules
    """
    不是在“相加”两个“分子”，你是在“相加”两个“列表
    1.列表1 + 列表2 是一个基本操作，它不代表数学上的“加法”，而是代表“列表合并” (Concatenation)。
    blue_molecules 是一个有 15 个元素的列表。  yellow_molecules 是另一个有 15 个元素的列表。   
    blue_molecules + yellow_molecules 的结果是： 一个新的、更长的列表，它把两个列表首尾相接。
    这个新列表 all_molecules 现在总共有 30 个元素（前面 15 个是蓝色的，后面 15 个是黄色的）
    """

    # 创建蓝色和黄色的粒子流对象
    blue_particle_stream = ParticleStream(screen_w, screen_h, PARTICLE_BLUE)
    yellow_particle_stream = ParticleStream(screen_w, screen_h, PARTICLE_YELLOW)

    # 字体设置,创建需要用到的字体对象
    title_font = pygame.font.SysFont("Simsun", 60, bold=True)
    button_font = pygame.font.SysFont("Simsun", 32, bold=True)
    tip_font = pygame.font.SysFont("Simsun", 18)

    # 开始按钮,定义“开始游戏”按钮的矩形区域
    start_button_rect = pygame.Rect(
        screen_w // 2 - 150,# 调整了X坐标，让它更居中
        screen_h // 2 + 40,
        300,
        60
    )
    # 定义按钮和提示的文字内容
    button_text = "开始游戏（按ENTER）"# 修改了文字
    tip_text = "方向键控制蛇移动 | Backspace在游戏中退出"

    # 开始界面的主循环，只要不退出或进入游戏，它就会一直运行
    while True:
        # 获取鼠标当前在屏幕上的位置
        mouse_pos = pygame.mouse.get_pos()
        # 检查鼠标是否悬停在按钮上
        mouse_hover_button = start_button_rect.collidepoint(mouse_pos)

        # 事件处理
        """
        pygame.event.get() 是 Pygame 游戏“心脏”的一部分。 它的来源是：Pygame 库的 event (事件) 模块。
        可以把 pygame.event.get() 想象成去你家的“信箱”取信
        “事件” (Event):玩家在你的游戏中做的每一个操作（按键盘、点鼠标、移动鼠标、点窗口的 'X' 退出），都是一个**“事件”**。
        你可以把每一个“事件”都想象成一封“信”。玩家按了回车键 -> 一封“回车键被按下”的信。   玩家点了鼠标 -> 一封“鼠标被点击”的信。
        玩家点了 'X' -> 一封“退出游戏”的信。
        Pygame 在后台做什么？
        Pygame 在后台有一个“邮递员”，它不停地在收集玩家产生的这些“信”（事件）。它把所有这些信，按照发生的顺序，放进一个**“队列”**里。
        这个“队列”就是你的**“信箱”**。
        pygame.event.get() 的功能是什么？
        这个函数的功能就是：“打开信箱，把里面 所有 堆积的信件 一次性 全部取出来，然后把它们装在一个‘列表’ (List) 里返回给你
        """
        for event in pygame.event.get():
            # 如果是退出事件（点击窗口的 'X'）
            if event.type == pygame.QUIT:
                pygame.mixer.music.stop() # 停止音乐
                pygame.mixer.quit() # 卸载mixer模块
                sys.exit() # 退出程序
            # 仅响应Enter键开始游戏
            """
            if  event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN:
            检查一下,玩家刚刚是不是（1）按下了键盘上的某个键，并且（2）那个键刚好是‘回车键’(Enter)
            2.pygame.KEYDOWN:   KEYDOWN 的意思是“按键被按下”。
            这是 Pygame 库定义的一个**“常量”**，它就代表“键盘按键被按下”这一类事件。
            """
            if event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN:
                sound_manager.play_sound("start_game") # 播放音效
                pygame.mixer.music.set_volume(0.6) # 提高游戏内音乐音量
                return "enter_game" # 返回信号，告诉主程序该进入游戏了
            # 如果是鼠标点击事件，并且鼠标在按钮上
            if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1 and mouse_hover_button:
                sound_manager.play_sound("start_game")
                pygame.mixer.music.set_volume(0.6)
                return "enter_game"

        # 绘制背景
        screen.fill(TECH_BG)

        # 绘制网格
        grid_spacing = 35
        for x in range(0, screen_w, grid_spacing):
            pygame.draw.line(screen, GRID_COLOR, (x, 0), (x, screen_h), 1)
        for y in range(0, screen_h, grid_spacing):
            pygame.draw.line(screen, GRID_COLOR, (0, y), (screen_w, y), 1)

        # 绘制粒子流
        blue_particle_stream.update()
        blue_particle_stream.draw(screen)
        yellow_particle_stream.update()
        yellow_particle_stream.draw(screen)

        # 绘制分子
        for mol in blue_molecules:
            mol.update()
            mol.draw(screen, all_molecules)
        for mol in yellow_molecules:
            mol.update()
            mol.draw(screen, all_molecules)

        # 5. 绘制渐变标题
        title_text = "欢迎来到我的贪吃蛇"
        GradientText.draw_gradient_text(
            screen, title_text, title_font,
            (screen_w // 2 - 260, screen_h // 2 - 120),
            TITLE_GRADIENT_START, TITLE_GRADIENT_END
        )

        # 绘制按钮
        # 根据鼠标是否悬停，选择不同的背景色,BUTTON_HOVER_BG&&BUTTON_BG是自己定义的常量，在上面
        button_bg_color = BUTTON_HOVER_BG if mouse_hover_button else BUTTON_BG
        # 画按钮的半透明背景
        pygame.draw.rect(screen, button_bg_color, start_button_rect, border_radius=8)
        """
        第一次调用 (画背景)：pygame.draw.rect(screen, button_bg_color, start_button_rect, border_radius=8)
        screen: 画在哪里？（画在主屏幕上）
        button_bg_color: 用什么颜色画？（用你刚刚选好的“背景色”）
        start_button_rect: 画在哪里，画多大？（根据 start_button_rect 这个矩形的位置和尺寸来画）
        border_radius=8:它的意思是“边框圆角半径”。你设置了 8，所以它画出来的矩形四个角是圆滑的，而不是 90 度的直角。
        (缺少 width 参数): 因为你没有指定 width (粗细) 参数，所以 draw.rect 默认会画一个“实心”的、被填充满的圆角矩形。
        效果：画出了按钮的“实心背景”。
        """
        # 根据鼠标是否悬停，选择不同的边框色
        border_color = MOLECULE_YELLOW if mouse_hover_button else MOLECULE_BLUE
        # 画按钮的边框
        pygame.draw.rect(screen, border_color, start_button_rect, width=3, border_radius=8)
        """
        pygame.draw.rect 是什么意思？ 这是 Pygame 库的 draw (绘制) 模块中，专门用来“画矩形”的函数
        第二次调用 (画边框)：pygame.draw.rect(screen, border_color, start_button_rect, width=3, border_radius=8)
        screen: 画在主屏幕上。
        border_color: 用什么颜色画？（用你刚刚选好的“边框色”）
        start_button_rect: 画在同一个位置。
        width=3: 这是关键！ 你指定了 width=3（粗细为 3 像素）。当 width 大于 0 时，draw.rect 就不会画实心，而是会画一个“空心”的边框。
        border_radius=8: 同样，这个边框也是圆角的。
        效果：画出了按钮的“空心边框”
        """

        # 渲染按钮上的文字
        button_text_surf = button_font.render(button_text, True, (255, 255, 255))
        # 获取文字的矩形，并将其中心与按钮的中心对齐
        button_text_rect = button_text_surf.get_rect(center=start_button_rect.center)
        # 把文字画到屏幕上
        screen.blit(button_text_surf, button_text_rect)

        # 绘制提示文字
        tip_surf = tip_font.render(tip_text, True, (150, 180, 220))
        tip_rect = tip_surf.get_rect(center=(screen_w // 2, screen_h - 30))
        screen.blit(tip_surf, tip_rect)

        # 播放背景音乐
        # 检查背景音乐是否播放完毕，如果是，则重新播放
        if not pygame.mixer.music.get_busy():
            sound_manager.resume_background_music()
        # 更新整个屏幕的显示
        pygame.display.update()
        # 控制游戏循环每秒最多运行60次
        clock.tick(60)

